Tutustu JavaScript Module Federationiin dynaamisten liitännäisjärjestelmien luomisessa. Opi arkkitehtuuri, toteutus, tietoturva ja parhaat käytännöt skaalautuvien ja ylläpidettävien sovellusten kehittämiseen.
JavaScript Module Federation -liitännäisarkkitehtuuri: dynaamisen liitännäisjärjestelmän rakentaminen
Nykypäivän monimutkaisessa web-kehityksen maailmassa modulaaristen, skaalautuvien ja ylläpidettävien sovellusten rakentaminen on elintärkeää. Yksi tehokas tekniikka tämän saavuttamiseksi on liitännäisarkkitehtuuri, jossa toiminnallisuus jaetaan itsenäisiin, dynaamisesti ladattaviin moduuleihin. JavaScript Module Federation, Webpack 5:n ominaisuus, tarjoaa vankan mekanismin tällaisten arkkitehtuurien toteuttamiseen. Tämä artikkeli syventyy Module Federationin käyttöön dynaamisen liitännäisjärjestelmän rakentamisessa.
Mitä on Module Federation?
Module Federation mahdollistaa JavaScript-sovellusten dynaamisen koodin jakamisen ajon aikana. Tämä tarkoittaa, että yhden sovelluksen moduulia (koodinpätkää) voidaan käyttää suoraan toisessa sovelluksessa ilman, että sitä tarvitsee rakentaa tai julkaista uudelleen. Tämä saavutetaan paljastamalla ja kuluttamalla moduuleja eri koontiversioiden ja jopa eri julkaisujen välillä.
Perinteiset koodinjakomenetelmät, kuten npm-paketit, vaativat kuluttavien sovellusten uudelleenrakentamista ja -julkaisua aina, kun jaettu riippuvuus päivitetään. Module Federation poistaa tämän ylimääräisen työn, mikä tekee siitä ihanteellisen tilanteisiin, joissa vaaditaan tiheitä päivityksiä ja itsenäisiä julkaisuja.
Miksi käyttää Module Federationia liitännäisarkkitehtuureissa?
Module Federation tarjoaa useita etuja liitännäisarkkitehtuurien rakentamisessa:
- Dynaaminen moduulien lataus: Liitännäisiä voidaan ladata ja poistaa ajon aikana, mikä antaa sovelluksille mahdollisuuden sopeutua muuttuviin vaatimuksiin ilman täydellistä uudelleenasennusta.
- Irrotus: Liitännäiset kehitetään ja julkaistaan itsenäisesti, mikä vähentää riippuvuuksia sovelluksen eri osien välillä.
- Skaalautuvuus: Sovellusta voidaan helposti laajentaa uusilla liitännäisillä vaikuttamatta olemassa olevaan toiminnallisuuteen.
- Ylläpidettävyys: Liitännäisiä voidaan päivittää ja ylläpitää itsenäisesti, mikä vähentää riskiä tuoda bugeja ydinosovellukseen.
- Koodin uudelleenkäyttö: Liitännäisiä voidaan käyttää uudelleen useissa sovelluksissa, mikä edistää yhtenäisyyttä ja vähentää kehitystyötä.
- Versiointi ja palautukset: Voit hallita liitännäisten eri versioita ja palata helposti aiempiin versioihin tarvittaessa.
Ydinkäsitteet: isäntä- ja etäsäiliöt (Host & Remote Containers)
Module Federation pyörii kahden keskeisen käsitteen ympärillä:
- Isäntäsäiliö (Host Container): Pääsovellus, joka kuluttaa etämoduuleja (liitännäisiä).
- Etäsäiliö (Remote Container): Sovellus, joka paljastaa moduuleja (liitännäisiä) isännän kulutettavaksi.
Isäntäsäiliö hakee dynaamisesti etäsäiliöstä etäkäynnistystiedoston (remote entry file), joka sisältää manifestin paljastetuista moduuleista. Isäntä voi sitten käyttää näitä moduuleja ikään kuin ne olisivat osa sen omaa koodikantaa.
Dynaamisen liitännäisjärjestelmän toteuttaminen Module Federationilla: askel-askeleelta-opas
Käydään läpi prosessi yksinkertaisen liitännäisjärjestelmän rakentamiseksi Module Federationin avulla. Luomme isäntäsovelluksen ja etäliitännäissovelluksen.
1. Isäntäsovelluksen (Host Container) pystyttäminen
Luo ensin uusi projektihakemisto ja alusta uusi npm-projekti:
mkdir host-app
cd host-app
npm init -y
Asenna Webpack ja sen riippuvuudet:
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
Luo `webpack.config.js`-tiedosto `host-app`-hakemistoon seuraavalla konfiguraatiolla:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
port: 3000,
hot: true,
static: {
directory: path.join(__dirname, 'dist'),
},
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'Host',
remotes: {
'plugin': 'Plugin@http://localhost:3001/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
Selitys:
- `name`: Isäntäsovelluksen nimi.
- `remotes`: Määrittää etäsäiliöt, joita isäntä kuluttaa. Tässä tapauksessa se kuluttaa `plugin`-nimistä etäsäiliötä osoitteesta `http://localhost:3001/remoteEntry.js`. `Plugin@`-syntaksi tarkoittaa, että etäsäiliön ModuleFederationPluginin `name` on 'Plugin'.
- `shared`: Luettelee riippuvuudet, jotka jaetaan isäntä- ja etäsäiliöiden välillä. Tämä estää näiden riippuvuuksien moninkertaisten kopioiden lataamisen. `shared`-asetuksen käyttö on kriittistä virheiden välttämiseksi ja liitännäisen oikean toiminnan varmistamiseksi.
Luo `src`-hakemisto ja lisää sinne `index.js`-tiedosto seuraavalla sisällöllä:
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
const PluginComponent = React.lazy(() => import('plugin/PluginComponent'));
const App = () => {
return (
<div>
<h1>Isäntäsovellus</h1>
<Suspense fallback={<div>Ladataan liitännäistä...</div>}>
<PluginComponent />
</Suspense>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Selitys:
- Käytämme `React.lazy`-funktiota `PluginComponent`-komponentin dynaamiseen tuontiin `plugin`-etäsäiliöstä. Tämä on ratkaisevan tärkeää liitännäisen laiskalataukselle (lazy loading) ja alkuperäisten latausviiveiden välttämiseksi.
- `Suspense`-komponenttia käytetään käsittelemään lataustilaa, kun liitännäistä haetaan.
Luo `public`-hakemisto ja lisää sinne `index.html`-tiedosto seuraavalla sisällöllä:
<!DOCTYPE html>
<html>
<head>
<title>Isäntäsovellus</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>
Lisää Babelin konfiguraatiotiedosto `.babelrc`:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
Päivitä `package.json`-tiedostosi käynnistysskriptillä:
{
"name": "host-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"html-webpack-plugin": "^5.6.0",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
2. Etäsovelluksen (Plugin Container) pystyttäminen
Luo uusi projektihakemisto liitännäiselle:
mkdir plugin-app
cd plugin-app
npm init -y
Asenna Webpack ja sen riippuvuudet:
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
Luo `webpack.config.js`-tiedosto `plugin-app`-hakemistoon seuraavalla konfiguraatiolla:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
port: 3001,
hot: true,
static: {
directory: path.join(__dirname, 'dist'),
},
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'Plugin',
filename: 'remoteEntry.js',
exposes: {
'./PluginComponent': './src/PluginComponent',
},
shared: ['react', 'react-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
Selitys:
- `name`: Etäsäiliön (liitännäisen) nimi. Tämän täytyy vastata isännän `remotes`-konfiguraatiossa käytettyä nimeä.
- `filename`: Etäkäynnistystiedoston (remote entry file) nimi, jonka isäntä hakee.
- `exposes`: Määrittää moduulit, jotka etäsäiliö paljastaa. Tässä tapauksessa paljastamme `PluginComponent`-moduulin. Avainta './PluginComponent' käytetään isännän tuontilausekkeessa (esim. `import('plugin/PluginComponent')`).
- `shared`: Kuten isännässä, luettelee jaetut riippuvuudet. On elintärkeää, että jaetut riippuvuudet ja niiden versiot ovat yhteensopivia isännän ja etäsäiliön välillä.
Luo `src`-hakemisto ja lisää sinne `PluginComponent.jsx`-tiedosto seuraavalla sisällöllä:
import React from 'react';
const PluginComponent = () => {
return (
<div style={{border: '1px solid blue', padding: '10px'}}>
<h2>Liitännäiskomponentti</h2>
<p>Tämä on dynaamisesti ladattu liitännäinen!</p>
</div>
);
};
export default PluginComponent;
Luo `index.js`-tiedosto `src`-hakemistoon viedäksesi PluginComponentin:
import PluginComponent from './PluginComponent';
export default PluginComponent;
Luo `public`-hakemisto ja lisää sinne `index.html`-tiedosto seuraavalla sisällöllä:
<!DOCTYPE html>
<html>
<head>
<title>Liitännäissovellus</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>
Lisää Babelin konfiguraatiotiedosto `.babelrc`:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
Päivitä `package.json`-tiedostosi käynnistysskriptillä:
{
"name": "plugin-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"html-webpack-plugin": "^5.6.0",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
3. Sovellusten ajaminen
Käynnistä sekä isäntä- että liitännäissovellus ajamalla `npm start` niiden omissa hakemistoissaan.
Siirry selaimessasi osoitteeseen `http://localhost:3000`. Sinun pitäisi nähdä isäntäsovellus dynaamisesti ladatun liitännäiskomponentin kanssa.
Edistyneet ominaisuudet ja huomioon otettavat seikat
Versiointi ja palautukset
Module Federation tukee versiointia, mikä mahdollistaa liitännäisten eri versioiden hallinnan. Voit määrittää versiorajoituksia isännän `remotes`-konfiguraatiossa. Esimerkiksi:
remotes: {
'plugin': 'Plugin@http://localhost:3001/remoteEntry.js@1.0.0',
}
Tämä käskee isäntää käyttämään liitännäisen versiota 1.0.0. Jos uudempi versio on saatavilla, isäntä jatkaa määritellyn version käyttöä, kunnes se nimenomaisesti päivitetään. Vankan versioinnin toteuttaminen on ratkaisevan tärkeää rikkovien muutosten estämiseksi ja sovelluksen vakauden varmistamiseksi.
Tietoturvanäkökohdat
Module Federationia käytettäessä tietoturva on ensisijaisen tärkeää. Harkitse seuraavia:
- Tunnistautuminen ja valtuutus: Toteuta asianmukaiset tunnistautumis- ja valtuutusmekanismit varmistaaksesi, että vain valtuutetut käyttäjät voivat käyttää liitännäisiä.
- Koodin eheys: Varmista etämoduulien eheys estääksesi haitallisen koodin syöttämisen sovellukseen. Harkitse Content Security Policyn (CSP) käyttöä rajoittaaksesi lähteitä, joista sovellus voi ladata resursseja.
- Riippuvuuksien hallinta: Hallitse huolellisesti sekä isäntä- että etäsäiliöiden riippuvuuksia haavoittuvuuksien välttämiseksi. Päivitä riippuvuudet säännöllisesti uusimpiin versioihin.
- Syötteen validointi: Vahvista kaikki etämoduuleilta saatu data estääksesi injektiohyökkäykset.
- CORS (Cross-Origin Resource Sharing): Määritä CORS oikein, jotta isäntäsovellus voi käyttää etäkäynnistystiedostoa liitännäissovelluksesta.
Liitännäisten löytäminen ja hallinta
Monimutkaisemmissa liitännäisjärjestelmissä saatat tarvita mekanismin liitännäisten löytämiseen ja hallintaan. Tämä voidaan saavuttaa liitännäisrekisterin tai löytämispalvelun avulla. Keskitetty rekisteri voi tallentaa tietoa saatavilla olevista liitännäisistä, mukaan lukien niiden sijainti, versio ja riippuvuudet. Isäntäsovellus voi sitten tehdä kyselyitä rekisteriin löytääkseen ja ladatakseen sopivat liitännäiset.
Harkitse näitä lähestymistapoja:
- Keskitetty konfiguraatio: Tallenna liitännäisten URL-osoitteet keskitettyyn konfiguraatiotiedostoon (esim. JSON-tiedosto), jonka isäntäsovellus lukee ajon aikana. Tämä mahdollistaa liitännäisten helpon lisäämisen, poistamisen tai päivittämisen ilman isäntäsovelluksen uudelleenjulkaisua.
- API-pohjainen löytäminen: Luo API-päätepiste, joka palauttaa luettelon saatavilla olevista liitännäisistä. Isäntäsovellus voi sitten hakea tämän luettelon ja ladata liitännäiset dynaamisesti.
- Tapahtumapohjainen arkkitehtuuri: Käytä tapahtumaväylää tai viestijonoa ilmoittaaksesi isäntäsovellukselle, kun uusia liitännäisiä on saatavilla. Tämä mahdollistaa asynkronisen liitännäisten löytämisen ja lataamisen.
Dynaaminen konfigurointi ja liitännäisten aktivointi
Mahdollisuus antaa käyttäjien dynaamisesti konfiguroida ja aktivoida liitännäisiä on tehokas ominaisuus. Tämä vaatii mekanismin liitännäiskonfiguraatioiden tallentamiseen ja hallintaan. Voit käyttää tietokantaa, konfiguraatiotiedostoa tai pilvipohjaista konfiguraatiopalvelua liitännäisasetusten tallentamiseen. Isäntäsovellus voi sitten lukea nämä asetukset ajon aikana ja aktivoida liitännäiset niiden mukaisesti. Harkitse käyttöliittymän tarjoamista liitännäiskonfiguraatioiden hallintaan.
Asynkronisten operaatioiden ja virheiden käsittely
Työskenneltäessä dynaamisesti ladattujen liitännäisten kanssa on olennaista käsitellä asynkroniset operaatiot ja virheet sulavasti. Käytä `async/await`-syntaksia tai Promiseja asynkronisen koodin hallintaan. Toteuta asianmukainen virheidenkäsittely, joka nappaa ja kirjaa kaikki liitännäisen latauksen tai suorituksen aikana ilmenevät virheet. Anna käyttäjälle informatiivisia virheilmoituksia. Harkitse keskitetyn virheidenkirjauspalvelun käyttöä virheiden seuraamiseksi kaikissa liitännäisissä.
Koodin jakaminen ja suorituskyvyn optimointi
Optimoidaksesi suorituskykyä, käytä koodin jakamista (code splitting) pilkkoaksesi sovelluksen ja liitännäiset pienempiin osiin. Tämä antaa selaimen ladata vain sen koodin, jota tarvitaan tietyllä sivulla tai tietyssä ominaisuudessa. Webpack tarjoaa sisäänrakennetun tuen koodin jakamiselle. Harkitse laiskalatauksen (lazy loading) käyttöä liitännäisten lataamiseksi vain tarvittaessa. Pienennä ja pakkaa koodi tiedostokoon pienentämiseksi.
Testaus ja jatkuva integraatio
Testaa liitännäisjärjestelmäsi perusteellisesti varmistaaksesi, että se toimii oikein. Kirjoita yksikkö-, integraatio- ja päästä-päähän-testejä (end-to-end). Käytä jatkuvan integraation (CI) järjestelmää testien automaattiseen ajamiseen aina, kun koodiin tehdään muutoksia. Toteuta jatkuvan toimituksen (CD) putki sovelluksen ja liitännäisten julkaisun automatisoimiseksi.
Tosielämän esimerkit ja käyttötapaukset
Module Federationia käytetään monissa tosielämän sovelluksissa, mukaan lukien:
- Verkkokauppa-alustat: Tuotesuositusten, maksuyhdyskäytävien ja toimituspalveluiden dynaaminen lataaminen. Esimerkiksi globaali verkkokauppa-alusta voisi käyttää Module Federationia integroidakseen eri maksupalveluntarjoajia asiakkaan sijainnin perusteella. Pohjois-Amerikassa se voisi ladata Stripen liitännäisen, kun taas Euroopassa se voisi ladata PayPalin tai Klarnan liitännäisen.
- Sisällönhallintajärjestelmät (CMS): Mahdollistaa käyttäjien asentaa ja aktivoida liitännäisiä CMS:n toiminnallisuuden laajentamiseksi. CMS voisi antaa käyttäjien asentaa liitännäisiä SEO-optimointiin, sosiaalisen median integraatioon tai sisältöanalytiikkaan.
- Kojelaudat ja analytiikka-alustat: Erilaisten widgettien ja visualisointien dynaaminen lataaminen. Globaali analytiikka-alusta voisi ladata liitännäisiä eri tietolähteille, kuten Google Analytics, Adobe Analytics tai Salesforce.
- Mikrofrontend-arkkitehtuurit: Suurten verkkosovellusten rakentaminen kokoelmana itsenäisesti julkaistavia mikrofrontendejä. Suuri yritys voisi käyttää Module Federationia rakentaakseen verkkosovelluksensa kokoelmana mikrofrontendejä, joista kukin vastaa tietystä liiketoimintatoiminnosta, kuten tilinhallinnasta, tuoteluettelosta tai tilausten käsittelystä.
- Design-järjestelmät: UI-komponenttien ja design-tokenien jakaminen useiden sovellusten välillä. Globaali organisaatio, jolla on useita brändejä, voisi käyttää Module Federationia jakamaan yhteisen design-järjestelmän kaikkien sovellustensa kesken, varmistaen yhtenäisyyden ja vähentäen kehitystyötä.
Parhaat käytännöt dynaamisten liitännäisjärjestelmien rakentamiseen Module Federationilla
Tässä on joitakin parhaita käytäntöjä, jotka kannattaa pitää mielessä, kun rakennat dynaamisia liitännäisjärjestelmiä Module Federationilla:
- Pidä liitännäiset pieninä ja kohdennettuina: Jokaisen liitännäisen tulisi vastata tietystä toiminnallisuudesta. Tämä helpottaa liitännäisten ylläpitoa ja päivittämistä.
- Määrittele selkeät liitännäisrajapinnat: Määrittele selkeät rajapinnat sille, miten liitännäiset ovat vuorovaikutuksessa isäntäsovelluksen kanssa. Tämä varmistaa, että liitännäiset ovat yhteensopivia isännän kanssa ja estää rikkovia muutoksia.
- Käytä semanttista versiointia: Käytä semanttista versiointia liitännäisten versioiden hallintaan. Tämä helpottaa muutosten seurantaa ja yhteensopivuuden varmistamista.
- Tarjoa dokumentaatio: Tarjoa selkeä ja ytimekäs dokumentaatio liitännäisillesi. Tämä auttaa käyttäjiä ymmärtämään, miten liitännäiset asennetaan, konfiguroidaan ja käytetään.
- Toteuta tietoturvan parhaat käytännöt: Noudata tietoturvan parhaita käytäntöjä suojataksesi sovelluksesi ja liitännäisesi haavoittuvuuksilta.
- Seuraa liitännäisten suorituskykyä: Seuraa liitännäistesi suorituskykyä pullonkaulojen tunnistamiseksi. Optimoi koodi suorituskyvyn parantamiseksi.
- Automatisoi julkaisu: Automatisoi sovelluksesi ja liitännäistesi julkaisu. Tämä vähentää virheiden riskiä ja varmistaa, että päivitykset julkaistaan nopeasti.
- Käytä yhtenäistä koodaustyyliä: Varmista yhtenäinen koodaustyyli kaikissa liitännäisissä. Tämä tekee koodista helpommin luettavaa ja ylläpidettävää.
- Kirjoita yksikkötestejä: Kirjoita yksikkötestejä liitännäisillesi varmistaaksesi, että ne toimivat oikein.
- Käytä linteriä: Käytä linteriä tarkistaaksesi koodisi automaattisesti virheiden varalta.
Yhteenveto
JavaScript Module Federation tarjoaa tehokkaan ja joustavan mekanismin dynaamisten liitännäisjärjestelmien rakentamiseen. Hyödyntämällä Module Federationia voit luoda modulaarisia, skaalautuvia ja ylläpidettäviä sovelluksia, jotka voivat mukautua muuttuviin vaatimuksiin. Noudattamalla tässä artikkelissa esitettyjä parhaita käytäntöjä voit rakentaa vakaita ja turvallisia liitännäisjärjestelmiä, jotka vastaavat organisaatiosi tarpeisiin.
Tämä teknologia on erityisen arvokas kansainvälisissä yhteyksissä, sillä se antaa yrityksille mahdollisuuden räätälöidä ohjelmistotarjontaansa tietyille alueille tai asiakassegmenteille ilman täysin erillisten sovellusten julkaisemista. Paikallisten maksuyhdyskäytävien integroinnista aluekohtaisen sisällön toimittamiseen, Module Federation mahdollistaa henkilökohtaisemman ja tehokkaamman käyttäjäkokemuksen maailmanlaajuisesti.